iT邦幫忙

2026 iThome 鐵人賽

DAY 3
0
AI Engineering

30 天打造 ResearchForge:從 AI 報告生成器到可追溯的研究工程系統系列 第 3

# Day 03|有利的參考資料,證據仍可能不成立

  • 分享至 

  • xImage
  •  

「尚未接受任何來源,匯出內容不會包含可驗證引用。」

這是 ResearchForge 早期引用功能裡的一則提醒。

但沿著當時的下載程式繼續往下看,會發現一件有些矛盾的事:系統顯示完這則警告之後,下一步仍然可以直接匯出檔案。

換句話說,系統已經知道來源存在缺口,卻還沒有因此把出口關上。[2][3]

上一篇談的是模板與匯出:一份文件能被交出去,不代表研究已經完成。

這一篇,我想把問題再往前推一步。

即使系統已經找到來源、整理好參考資料,甚至在每個句子後面放上正式的引用,我還是得繼續問:

那個引用,真的支持前面的那句話嗎?

要回答這個問題,不能只看引用格式漂不漂亮,而必須先弄清楚:ResearchForge 當時到底保存了什麼資料,又真正檢查到了哪一層。


引用存在,和證據成立,是兩件不同的事

引用最容易造成的一種錯覺,是「看起來有根據」。

一個句子可以文法正確、前後連貫,句尾也附著一個正式的 [1]。但文末列出一篇論文,只代表讀者能找到那個來源;句尾出現引用標記,也不代表來源內容真的支持這個句子的全部主張。

可以用一個假設例子理解。

假設某份研究只記錄:

某款工具在特定任務中的表現有所提升。

報告卻把它寫成:

這款工具適合所有工作。

即使引用確實指向那份研究,這個句子的範圍仍然超出了原始材料。

問題不在於「引用是不是假的」,而在於來源提供的證據範圍,沒有大到足以支撐報告做出的結論

這不是 ResearchForge 當時某一次真實報告中實測出的錯誤,而是我用來區分兩件事情的例子:

  • 有沒有引用;
  • 引用是否真的支持主張。

ResearchForge 在這個階段,首先處理的是比較基礎的前者。

2026 年 6 月 12 日的 citation foundation milestone 加入了來源搜尋、共同資料格式與來源審查狀態,讓來源不再只是報告最後附上的一行文字,而是正式進入系統資料流程中的一筆紀錄。[1]

這還不是逐句證據查核。

但至少從這裡開始,我可以回答一個以前不容易回答的問題:

「這份報告目前到底用了哪一筆來源?」


第一步不是驗證內容,而是先知道「這是哪一筆資料」

當時的搜尋程式接上 Crossref 與 Semantic Scholar。

兩個服務回傳的資料格式並不完全相同,因此 ResearchForge 會先把搜尋結果整理成共同的 SourceRecord,也就是一筆標準化的來源紀錄。

其中包含標題、作者、年份、摘要、DOI、網址、來源服務,以及審查狀態等欄位;前後端都依照同一套欄位約定處理。[1]

這件事乍看只是資料整理,但其實很重要。

如果不同搜尋服務回傳的作者、年份、網址都用完全不同的結構表示,那麼後面的審查、引用、匯出甚至錯誤追查,都會被資料格式本身拖住。

共同格式至少先讓每一筆來源有一致的「身分」。

DOI 在這裡主要用來識別文獻。

至於標題、作者、年份等資訊,通常統稱為 metadata,也就是「中繼資料」。它們可以幫助系統辨認與呈現一篇來源,卻不能取代來源實際寫了什麼。

知道一篇文章叫什麼,和知道它支持什麼,是兩件完全不同的工作。

共同資料格式解決的是前者。

例如審查畫面可以用同一套欄位顯示來源,而不必因為資料來自 Crossref 或 Semantic Scholar,就各自另外寫一套作者、年份或標題的處理邏輯。

當時的介面也已經可以編輯標題、作者、年份等資料,並把來源標記為接受或拒絕。[1][3]

這讓來源開始變成可以被單獨檢查、修改與追蹤的對象,而不是直接混進整篇報告裡。


缺少的資料,也必須真的保持「缺少」

建立共同資料格式之後,還有另一個很容易踩到的問題:

如果來源資料不完整,系統要不要幫它補完整?

ResearchForge 當時的做法,是不要猜。

歷史程式會把沒有取得的年份、摘要或 DOI 保留為 null;如果沒有作者,則保留空清單。[1]

整理程序可以清除多餘空白、統一資料形狀,但不會因為欄位看起來應該存在,就自己推測一個作者、年份或 DOI 填進去。

這個設計原則後來對我很重要:

共同格式的目的,不是讓每一格看起來都有資料,而是讓缺口也能被明確表示。

如果不知道,就留下不知道。

否則資料結構雖然變完整了,證據反而可能變得更不可信。


accepted 代表「被選用」,不是「已被證明」

來源進入系統後,還需要經過審查。

搜尋回來的來源預設會是 pending,也就是「待審查」。

使用者之後可以把它改成:

  • accepted:接受;
  • rejected:拒絕。

當時的前端會先挑出 accepted 的來源,再放進報告生成輸入;參考資料的組裝也會篩選這個狀態。[1][2][3]

下面是當時共同來源模組中的原始程式節錄:

export function acceptedSourceRecords(sources: SourceRecord[]): SourceRecord[] {
  return sources.filter((source) => source.reviewState === "accepted");
}

export function rejectedSourceRecords(sources: SourceRecord[]): SourceRecord[] {
  return sources.filter((source) => source.reviewState === "rejected");
}

filter 會保留符合條件的資料。

第一個函式只留下 accepted 的來源;第二個則只留下 rejected 的來源。pending 不會通過其中任何一個。

但真正重要的是:這段程式只讀了 reviewState

它沒有查看摘要。

沒有把來源原文和報告句子逐句比較。

也沒有判斷「這篇論文是不是足以支持這個結論」。

所以:

accepted 的真正意思,是「這筆來源在目前工作流程中被選用了」。

而不是:

「這份來源已經證明所有引用它的句子都正確。」

這兩句話看起來只差一點,實際上卻是完全不同的品質承諾。

這也讓我開始重新注意介面上的措辭。

「已接受來源」可以描述一個操作狀態。

但如果介面把它寫成「內容已驗證」,就代表系統承諾了程式根本沒有完成的檢查。

現在回頭看,我會先問:

這個狀態到底是由哪一個動作產生的?

再決定能不能把它當成品質標籤。


發現缺口,和阻止錯誤繼續流出去,也不是同一件事

早期 ResearchForge 並沒有把「來源被接受」和「來源資料完整」綁在一起。

即使一筆來源已經是 accepted,它仍然可能缺少作者、年份、DOI 或網址。

參考資料格式化時,程式會標出缺少的欄位;相關測試也刻意建立缺少作者與年份的測試資料,用來確認系統會保留「未提供」的狀態,而不是自行補造內容。[2]

這些測試可以證明一些很具體的事情:

  • 缺失資料沒有被偷偷補上;
  • 被拒絕的來源不會進入參考資料;
  • 來源狀態能確實影響輸出。

但它們不能證明:

  • 搜尋服務每次都找到了正確的研究;
  • 搜尋結果本身品質良好;
  • 每一個引用都真的支持報告中的句子。

因為這些測試檢查的是資料處理規則,不是來源與主張之間的語意支持關係。[1][2]

這也是為什麼文章開頭那則匯出提醒值得特別注意。

當時的匯出檢查會確認幾件事情:

  • 是否完全沒有已接受來源;
  • 來源資料是否不完整;
  • 報告裡是否還留著待補引用的文字。

以 Markdown 下載流程為例,程式會先呼叫提醒函式,然後繼續呼叫下載函式;生成按鈕本身,也沒有因為來源不足而被停用。[2][3]

所以那個流程實際上是:

系統知道這份報告可能還有問題 → 顯示警告 → 仍然允許匯出。

這不是提醒失敗。

提醒其實很誠實。

真正的問題是:

提醒和阻擋,本來就是兩種不同的機制。

提醒的作用是告訴使用者:

這裡還有事情沒有完成。

阻擋的作用則是決定:

如果這件事情沒有完成,系統還允不允許流程繼續。

如果只確認「有沒有警告」,很容易誤以為風險已經被處理。

但對研究工具來說,更重要的問題往往是:

當風險真的存在時,出口到底有沒有被關上?


這一階段真正解決的,是來源身分,不是證據驗證

還有一個不能被功能名稱掩蓋的邊界。

當時 ResearchForge 的報告生成器,仍然是依規則組裝內容的模擬實作,並不是一套已經讓語言模型完整閱讀文獻、再逐句驗證主張的系統。[3]

因此,這個階段真正完成的是:

來源正式進入審查與報告資料流。

而不是:

自動研究與證據驗證已經完成。

兩者距離仍然很遠。

不過現在回頭看,我仍然認為先把來源變成可追蹤的共同紀錄是必要的。

因為如果有一天,一筆不應該出現的來源進入了參考資料,我至少可以沿著資料流往回查:

這是哪一筆 SourceRecord

它原本是 pendingaccepted 還是 rejected

哪一段程式篩選了它?

哪一個流程把它送進報告?

哪一個步驟產生了參考資料?

至少問題開始有一條可以追蹤的路。

至於更困難的那一層——

這份來源到底有沒有支持報告裡的那個句子?

仍然必須另外核對。

不能期待一個 accepted 狀態,替整個證據鏈回答所有問題。


我現在會怎麼追一筆引用

如果你也正在做自己的 AI 報告工具,我覺得可以先不用急著設計一個「可信度分數」。

先隨便挑一筆來源,沿著它把整條資料流走一次。

先看:

搜尋完成後,系統到底保存了哪些欄位?

哪些欄位是真的從來源取得,哪些只是 metadata?

接著看:

誰把它從 pending 改成 accepted

這個接受動作到底代表什麼?

再往後看:

哪些輸出真的有讀取 reviewState

被拒絕的來源是否確實消失?

缺失欄位是否真的保持缺失?

最後,再挑報告中的一句話。

回到原始材料裡,確認來源真正能支持到哪一個範圍。

如果找不到對應內容,就把這個缺口留下來。

不要因為句尾已經有一個漂亮的 [1],就讓引用格式替證據本身背書。

這是我從 ResearchForge 早期實作整理出來的一套追查方法,而不是它當時已經具備的完整防護。

但我慢慢發現,這種追查方式比直接問「系統到底準不準」更有用。

因為每一個暫時答不出來的問題,都可以被拆成下一個明確的工程工作:

來源沒有身分,就先建立來源紀錄。

審查狀態語意不清,就先重新定義狀態。

缺口只有提醒、沒有阻擋,就決定哪些條件應該成為 gate。

主張和來源沒有建立對應,就再往證據驗證那一層前進。

不需要一開始就把整份 AI 報告當成一個難以理解的黑箱。


當來源終於能被辨認、整理與追蹤之後,下一個問題也自然出現了:

進入審查清單裡的這些資料,一開始究竟是怎麼被找到的?

下一篇,我會沿著查詢流程再往前追。

從一個搜尋框開始,看看為什麼 ResearchForge 後來需要的不只是「搜尋得到結果」,而是一條能夠診斷、能夠知道自己在哪裡失敗的搜尋流程

參考資料

  1. ResearchForge 專案 Git 歷史,〈citation foundation milestone〉中的來源搜尋、共同來源模型與來源轉換測試。用於核對資料欄位、預設審查狀態及缺失處理;本文依歷史原始碼說明,不將測試資料當成線上搜尋成果。
  2. 同一版本的來源審查輔助程式與測試。用於核對接受/拒絕篩選、參考資料組裝、缺漏標記與匯出提醒;這些檢查不等同主張與來源之間的支持關係查核。
  3. 同一版本的生成器介面與報告生成程式。用於核對來源編輯、接受來源的輸入路徑、模擬生成,以及提醒後仍可匯出的實際控制流程。

上一篇
# Day 02|模板先完成,證據仍然缺席
下一篇
# Day 04|搜尋開始成為一條流程:API 回應只是第一站
系列文
30 天打造 ResearchForge:從 AI 報告生成器到可追溯的研究工程系統6
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言